/**
 * Created by dell2 on 2016/12/14.
 * 遍历数据库，查找未完成事物的操作任务，重做根据如下字段查询数据库
 *  state : "begin",              //事物状态,begin 开启事物  ,committed 完成事物 ,finished事物已经回滚 error事物执行异常
    lastModified : new Date()    //lastmodified < now()-1 min
 */
var TransactionsDB = require('../../db/transactions'),
    events = require('events') ,                // 引入 events 模块
    eventEmitter = new events.EventEmitter() , // 创建 eventEmitter 对象
    TASK_FINISH_EVENT = "job_done" ;            //define a event represent finishing a job
    WAIT_EXIT_TIME = 3600 * 1000 ;              //wait for a hour 3600
    logger = require('../log4js/log4js').logger('support.js'),
    transactions = new TransactionsDB(),
    _k8s_adp = require('../k8s/k8sAdpter');
    storageAdpt = require('../storage/storageAdapter'),
    _handle = storageAdpt.getStorageHandle(),
    appConfigHistoryDB = require('../../db/appConfigHistoryDB'),
    confHistoryDB = new appConfigHistoryDB(),
    serviceMetaInfoDB = require('../../db/serviceMetaInfoDB'),
    svcMetaInfoDB = new serviceMetaInfoDB(),
    appConfigHistoryDB = require('../../db/appConfigHistoryDB'),
    confHistoryDB = new appConfigHistoryDB(),
    Tx = require('../tx/TxManager') ,
    txManager = new Tx() ;
   // var Q = require('q'),
//  result = Q('begin');

//the main process
main() ;

function setProcessTitle( title ){
    process.title = title ; //set the title of current process.
}

function main(){
    var count = 0 ,
        dateThreshold = new Date();
        dateThreshold.setMinutes(dateThreshold.getMinutes() - 1); //
    var query = {
        state : "begin",
        lastModified: { $lt: dateThreshold }
    } ;
    setProcessTitle( "sub_process_restore" ) ;
    logger.info("sub_process started!!" + process.title ) ;
    traverseCollection(query ).then(function (ret) {
        //logger.info( "get data:" +JSON.stringify( ret )) ;
        register( ret.length ) ;
        if(ret && ret.length > 0 ){
            //logger.info( "test test test !!!" ) ;
            ret.forEach(function ( one ) {
                handleOne( one ) ;
            }) ;
        }else{
            exit() ;
        }
       // exit()
    }).catch(function (err) {
        logger.error("error:"+JSON.stringify( err )) ;
        exit() ;
    }) ;
}

/**
 * traverse the collections for searching the unfinished jobs
 * we suppose the unfinished jobs will not be much,so here will not use paging
 */
function traverseCollection(query ) {
    query = query || {} ;
    return new Promise(function ( resolve,reject ) {
        transactions.getWithParam(null, query, function (err,rets) {
            if (err) {
                reject(err);
            } else {
                resolve( rets );
            }
        });
    }) ;
}

/**
 * a sample of transactions record
 * {
		"_id" : "585139de794f43200ecaa8bf",
		"data" : {
		   app:xx,
		   id:xx,
		  ...
		},
		"expiredAt" : null,
		"lastModified" : "2016-12-14T12:23:58.878Z",
		"operation" : "deleteApplication",
		"source" : "deleteApplication#nature01:gee18619383-6992-44a3-9a43-4b01e97a7da0",
		"state" : "begin"
	}
     source    operation +'#'+namespace+':'+app(or id)
 * @param tx
 */
function handleOne( tx){
    if( tx ){
        var oper= tx.operation ||  "" ;
        var username = tx.source ;
        var op_index = tx.source.indexOf("#") ;
        var user_index = tx.source.indexOf(":") ;
        username = username.substring( op_index+1 , user_index ) ;

        try {
            begin( tx ).then(function () {
                logger.debug("restore start ") ;
                if (oper === txManager.CREATEAPP) {
                    redoCreateApplication(username, tx.data);
                }
                if (oper === txManager.UPDATEAPP) {
                    redoUpdateApplication(username, tx.data);
                }
                if (oper === txManager.DELETEAPP) {
                    redoDeleteApplication(username, tx.data);
                }
                if (oper === txManager.MODIFYRC) {
                    redoModifyRc(username, tx.data);
                }
                if (oper === txManager.GRAYUPDATE) {
                    redoGrayudp(username, tx.data);
                }
                if (oper === txManager.CREATECOMP) {
                    redoCreateComp(username, tx.data);
                }
                if (oper === txManager.DELETECOMP) {
                    redoDeleteComp(username, tx.data);
                }
                if (oper === txManager.UPDATECOMP) {
                    redoUpdateComp(username, tx.data);
                }
                if (oper === txManager.CREATESTORAGE) {
                    redoCreatestorage(username, tx.data);
                }
                if (oper === txManager.DELSTORAGE) {
                    redoDelstorage(username, tx.data);
                }
            }).catch(function ( err ) {
                throw err ;
            }) ;
            goodEnd(tx) ;
        }catch( err ){
            errorHandler( tx , err ) ;
        }
    }
    function begin( tx ){
        var bid = tx.source ;
        tx.source =  tx.source+"_restore" ;
        return TxSupport().beginRestore( bid, tx.source  ) ;
    }
    function goodEnd( tx ){
        //commit the current transaction and open a new one .
        //there will maybe a time lag before open a new ,but here omit it .
        TxSupport().commit( tx.source  ) ;
    }
    function errorHandler(tx ,err) {
        loggerError( err ) ;
        TxSupport().setExceptionStatus( tx.source ) ;
    }
}

/**
 * hard code below start
 * really really hate to change the structure of this project
 * and hate to modify the code too .
 * So if you are a new guy who assigned this job(refactoring),sorry,sorry,sorry
 * because i'm not an architect ,so i can't do more but ctrl+c and ctrl+v
 */
function redoCreateApplication(_username,_svc ) {

    _k8s_adp.getK8s(_svc.node.clusterId).then(function(_k8s){
        return _k8s.createAppService(_username,_svc);
    }).then(function () {
        loggerInfo(_svc.app+" has been created!!!") ;
        notify() ;
    }).catch(function (err ) {
        //delete it if failed
        _k8s.deleteAppService(_username,_svc,false).catch(function ( err ) {
            throw err ;
        });
    }) ;
}
function redoDeleteApplication(_username,_svc  ){
   try{
       _k8s_adp.getK8s(_svc.node.clusterId).then(function(_k8s){
           logger.debug("is branch passed here ?") ;
           return _k8s.deleteAppService(_username,_svc,false); //默认不删除卷，缺少卷的数据所以无法删除
       }).then(function(){
           loggerInfo(_svc.app+" has been deleted!!!") ;
           notify() ;
       }).catch(function (err ) {
           throw err ;
       }) ;
   }catch(err){
       logger.error( "error occurred here ",err  ) ;
   }

}
function redoUpdateApplication( _username,_data  ) {
    var _svc = _data ; //copy
    _k8s_adp.getK8s(_svc.oldsvc.node.clusterId).then(function(_k8s){
        return _k8s.updateAppService(_username,_data.newsvc,_data.oldsvc);
    }) .then(function(){
        loggerInfo(_svc.app+" has been updated!!!") ;
        notify() ;
    }).catch(function (err ) {
        throw err ;
    }) ;
}
function redoModifyRc ( username, svc ){
    // rcnum and rcid are not saved in the data ,so we can't restore the operation before crash
    adjustStatus( username,svc ) ;
}
function redoGrayudp(_username,svc ){
    adjustStatus( _username,svc ) ;
}
function redoCreateComp( _username,_data ) {
    _k8s_adp.getK8s('default').then(function(_k8s){
        return _k8s.createCompose(_username,_data);
    }).then(function(){
        loggerInfo(_data.id+" has been created!!!") ;
        notify() ;
    }).catch(function(err){
        throw err ;
    });
}
function redoDeleteComp(_username, _data  ) {
    
    _k8s_adp.getK8s('default').then(function(_k8s){
        return _k8s.getServiceFromCompId(_username,_data.id);
    }).then(function(services) {
        _data.services = services;
         return _k8s.deleteCompose(_username, _data);
    }).then(function(){
        notify() ;
        loggerInfo(_data.id+" has been deleted !!!") ;
    }).catch(function(err){
        throw err ;
    });
}
function redoUpdateComp(_username,_data ) {
    
    _k8s_adp.getK8s('default').then(function(_k8s){
        return _k8s.updateCompose(_username,_data);
    }).then(function(){
        notify() ;
        loggerInfo(_data.id+" has been updated!!!") ;
    }).catch(function(err){
        throw err ;
    });
}
function redoCreatestorage(_username,_volumn ) {
    //logger.debug( "redoCreatestorage" ) ;
    //_handle.createVolumeWithCheckEndpoints(_username,_volumn.volume).then(function(ret){
    _handle.createVolume(_username,_volumn.volume).then(function(ret){
        notify() ;
    }).catch(function(err){
        throw err ;
    })
}
function redoDelstorage(_username,item ) {
    //_handle.deleteVolume(_username,item).then(function(ret){
    _handle.deleteVolume(item).then(function(ret){
        notify() ;
    }).catch(function(err){
        throw err ;
    });
}

function loggerError( e ) {
    logger.error("Failed when restoring ",e ) ;
}
function loggerInfo( msg ) {
    logger.info( msg ) ;
}

function adjustStatus( username,svc ) {
    var app = svc.app ;
    var ns = username ;

    _k8s_adp.getK8s(svc.node.clusterId).then(function(_k8s){

        try{
            // just adjust the data in db by querying k8s
            _k8s.getAppRCSimple( ns, app, null ).then(function ( rcList) {
                //logger.debug("get rc simple processing ...") ;
                if( rcList && rcList.length > 0  ){
                    rcList.forEach(function ( rcOne) {
                        var adjust ={
                            rcnum: rcOne.required ,
                            image: rcOne.image ,
                            totalStatue:"updating",
                            ver: rcOne.ver
                        } ;
                        adjustTheStatusInDb(null,username,svc,adjust).then(function () {
                            notify() ;
                        }).catch(function ( err ) {
                            throw err ;
                        }) ;

                    });
                }
            }).catch(function ( err ) {
                throw  TxSupport().rollBackFailure( err ) ;
            }) ;
        }catch(exe){
            loggerError( exe ) ;
            throw exe ;
        }


    }) ;
}
/**
 *
 * eg. adjust ={
 *  rcnum:1,
 *  image:xx,
 *  totalStatue:xx,
 *  ver:xx
 * }
 */
function adjustTheStatusInDb(composeId,username,svc,adjust){
    //hi,if you are a new guy in this project
    //sorry ,sorry,sorry ,i can extract a method in kubernetes.js but i preferred ctrl_c and ctrl_v
    adjust = adjust || {} ;
    return new Promise(function (_resolve, _reject) {
        var cond = {};
        if (composeId !== undefined)
            cond = {composeId: composeId, app: svc.app};
        else
            cond = {username: username, app: svc.app};
        //update rc object & svc objcect
        svc.ver = adjust.ver;
        svc.node.image = adjust.image ;
        svc.totalStatue = adjust.totalStatue ;
        svc.rcnum = adjust.rcnum ;

        //update db
        svcMetaInfoDB.updateWithParam(null, cond, svc, function (err,rets) {
            if (err) {
                logger.error("[grayUpdate]update svcMetaInfo failed,"+JSON.stringify(err)) ;
                _reject(err);
            } else {
                //logger.debug( "update db.result :" +JSON.stringify( rets ) ) ;
                _resolve(svc);
            }
        });
    });
}

/**
 * rude and can solve almost all the problem happen when recreate the app
 * sorry,sorry,sorry.if you are new guy ,please think of how to refactor and extract good functions.
 */
function rudeDelete(namespace,app,version){

}

//since the restoring procedure will be very complicated
//ASAP, the real implement will be simplified
//just like Last-straw ,the people build up this project want to add every things in node.
// so if your are new and you are reading and meet a lot problems in this project
//open your mind  ,try to split the services into several parts which called Micro service
//node is not so appropriate for the async job-based project.

/**
 * adapter fo transactionManager
 * @constructor
 */
function TxSupport() {
    return {
        beginRestore:function (bid , newbid) {
            return txManager.beginRestore(bid,newbid ) ;
        },
        setExceptionStatus:function (bid) {
            txManager.setErrStatus( bid ) ;
            throw {
                name:"TxError",
                message:"An unhandlable error occurred"
            }; //无法再
        },
        commit:function (bid) {
            return txManager.commit(bid ) ;
        },
        getBidFromSvc:function(username ,svc){
            //通过用户名和id或者app作为唯一主键
            svc = svc || {} ;
            var rand = "" ;
            if( username.indexOf("create") === -1 ){
                rand =txManager.uuid() ; //除了创建和需要唯一，其他操作都可以重复操作多次
            }

            if( username && svc.app ){
                return username+":"+svc.app +rand ;
            }else if( username && svc.id){
                return username+":"+svc.id +rand ;
            }else{
                throw "No username or app name specified in svc object" ;
            }
        },
        isObject : function(obj){
            return Object.prototype.toString.call(obj) === '[object Object]';
        },
        rollBackFailure : function(msg){
            return {
                ret:false,
                data: msg ,
                isFatal:true,
                message:msg,
                name:"RollBackFailure"
            };
        }

    } ;
}

function register( TARGET){
    var count = 0 ;
    eventEmitter.on( TASK_FINISH_EVENT , function ( data ) {
        count ++ ;
        if( count === TARGET){
            //all the jobs are finished .then exit
            setTimeout( exit ,WAIT_EXIT_TIME);
        }
    });
}
function notify() {
    eventEmitter.emit( TASK_FINISH_EVENT );
}

/**
 * destroy the child process when it accomplish its mission
 */
function exit(){
    logger.warn( "The time to exit ... " ) ;
   process.exit(0) ;
}

